home *** CD-ROM | disk | FTP | other *** search
/ POINT Software Programming / PPROG1.ISO / pascal / swag / tsr.swg / 0023_A Working TSR.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1994-02-03  |  22.0 KB  |  814 lines

  1. {
  2. ---------------------------------------------------------------------------
  3.  MD> How do you write something so it is interup driven?
  4.  
  5.  MD> Can anyone post some simple code to perhaps count a number
  6.  MD> up by one and display it to the screen while at the same
  7.  MD> time allowing another part of the program do what ever it's
  8.  MD> suppose to.?
  9.  
  10. Marek:
  11.  
  12. The features to which you refer constitute writing a TSR program.
  13. These programs are executed and terminate, but stay resident in
  14. system memory.  Prior to termination they insert themselves into
  15. the Interrupt Service Routine chain of a known interrupt vector.
  16.  
  17. This programming was simplified somewhat in turbo Pascal Release
  18. 4 or 5 with the addition of the Keep, GetIntVec and SetIntVec
  19. procedures of the DOS unit.  However, these procedures are only
  20. a small fraction of the coding needed to write reliable TSR's.
  21. This fact explains the lack of simple code examples.
  22.  
  23. To write good TSR's you need to read about them and play with
  24. someone else's code for a while. (I've coded pascal for 17 years,
  25. but my first TSR took about 3 days to debug).  For a solid
  26. Pascal reference, try Tom Swan's 'Mastering Turbo Pascal'.
  27. To figure out what the interrupt functions are doing, I recommend
  28. Ralf Brown's & Jim Kyle's 'PC Interrupts'.  The book is derived
  29. from their well-known interrupt list (INTERnn.ZIP) available
  30. on many programming oriented BBS systems.  The index of the book
  31. is well worth the money.  Finally, I recommend Ed Mitchell's
  32. "Borland Pascal Developer's Guide". Ed's TSR code is thorough
  33. and documented with education in mind.  Since I'm quoting
  34. ED's book here, please buy a copy if you find the code useful.
  35.  
  36. Good luck
  37.  
  38. -------------------------------------------------------------------------
  39.                              TSR Code Example
  40. -------------------------------------------------------------------------
  41. }
  42.  
  43. { TSR.PAS
  44.   Sample TSR application written in Turbo Pascal.
  45.   IMPORTANT!
  46.   This TSR operates only in TEXT mode, and, as
  47.   written, supports only 80 x 25 sized screens
  48.   (not 43- or 50- line display modes).
  49.  
  50.   This TSR will only operate on DOS 3.x or newer
  51.   versions of DOS.
  52.  
  53.   Use this code as an example to write your own TSR code.
  54.   Modify the $M compiler directive, below, to specify
  55.   the maximum stack size, minimum heap and maximum heap
  56.   required for your TSR application.
  57.  
  58.   Please see the text and other source comments
  59.   for important restrictions.
  60.  
  61.   TSRs can be DANGEROUS, so be careful.
  62. }
  63.  
  64. {$S-}
  65. {$M 3072, 0, 512}
  66. uses
  67.   Crt, Dos;
  68.  
  69. type
  70.   { Defines an array to store the screen image.
  71.   For greater efficiency, your code may want to save only
  72.   the portion of the screen that is changed by your TSR
  73.   code. This implementation saves the entire screen image,
  74.   at popup time, for the greatest flexibility. }
  75.   TSavedVideo=Array[0..24, 0..79] of Word;
  76.   PSavedVideo=^TSavedVideo;
  77.  
  78.  
  79. { The following items define the TSR's identification
  80. string. }
  81. type
  82.   String8=String[8];
  83. const
  84.   IdStr1:String8='TP7RTSR';
  85.   IdStr2:String8='TSRInUse';
  86.  
  87. var
  88.   CPURegisters: Registers;
  89.     { General register structure for Intr calls. }
  90.   CursorStartLine: Byte;
  91.     { Stores cursor shape information. }
  92.   CursorEndLine: Byte;
  93.     { Stores cursor shape information. }
  94.   DiskInUse: Word;
  95.     { Tracks INT 13 calls in progress. }
  96.   MadeActive: Boolean;
  97.     { TRUE if this TSR has been asked to pop up. }
  98.   OurSP: Word;
  99.   OurSS: Word;
  100.     { Saved copies of our SS and SP registers. }
  101.   PInt09: Pointer;
  102.     { Saved address of keyboard handler. }
  103.   PInt12: Pointer;
  104.     { Saved address of GetMemorySize interrupt. }
  105.   PInt1B: Pointer;
  106.     { Ctrl-Break interrupt address. }
  107.   PInt24: Pointer;
  108.     { DOS Critical error handler. }
  109.   PInt28: Pointer;
  110.     { Saved address of background task scheduler. }
  111.   PInt1C: Pointer;
  112.     { Saved address of timer handler. }
  113.   PInDosFlag: ^Word;
  114.     { Points to DOS's InDos flag. }
  115.   VideoMem: PSavedVideo;
  116.     { Points to actual video memory area. }
  117.   SavedVideo:TSavedVideo;
  118.     { Stores the video memory when TSR is popped up. }
  119.   SavedWindMin: Word;
  120.    { Holds saved copy of WindMin for restoring window. }
  121.   SavedWindMax: Word;
  122.    { Holds saved copy of WindMax for restoring window. }
  123.   SavedSS,
  124.   SavedSP: Word;
  125.    { Saves caller stack registers; must be global
  126.    to store in fixed memory location, not on local
  127.    stack of interrupted process. }
  128.   SavedX,
  129.   SavedY: Word;
  130.     { Stores X, Y cursor prior to TSR popup }
  131.     { for restoration when TSR goes away. }
  132.   TempPtr: Pointer;
  133.     { Used internally to DoUnInstall. }
  134.   TSRInUse: Boolean;
  135.     { Set TRUE during processing to avoid double
  136.     activation. }
  137.  
  138.  
  139.  
  140. procedure SaveDisplay;
  141. { Copies the content of video memory to an internal
  142.   array structure. Saves the cursor location and cursor
  143.   shape definitions. }
  144. var
  145.   CursorLines: Byte;
  146. begin
  147.   { Save cursor location. }
  148.   SavedX := WhereX;
  149.   SavedY := WhereY;
  150.   { Saved existing window values. }
  151.   SavedWindMin := WindMin;
  152.   SavedWindMax := WindMax;
  153.  
  154.   { Get and save current cursor shape. }
  155.   with CPURegisters do
  156.   begin
  157.     AH := $03;
  158.     BH := 0;
  159.     Intr($10, CPURegisters);
  160.     CursorStartLine := CH;
  161.     CursorEndLine:= CL;
  162.   end;
  163.  
  164.   { Get equipment-type information. If Monochrome
  165.     adapter in use, then point to $B000; otherwise use the
  166.     color memory area. }
  167.   Intr( $11, CPURegisters );
  168.   if  ((CPURegisters.AX shr 4) and 7) = 3 then
  169.   begin
  170.     VideoMem := Ptr( $B000, 0 );
  171.     CursorLines := 15;
  172.   end
  173.   else
  174.   begin
  175.     VideoMem := Ptr( $B800, 0 );
  176.     CursorLines := 7;
  177.   end;
  178.   SavedVideo := VideoMem^;
  179.  
  180.   { Change cursor shape to block cursor. }
  181.   with CPURegisters do
  182.   begin
  183.     AH := $01;
  184.     CH := 0;
  185.     CL := CursorLines;
  186.     Intr($10, CPURegisters);
  187.   end;
  188.  
  189. end; { SaveDisplay }
  190.  
  191.  
  192. procedure RestoreDisplay;
  193. { Always called sometime after calling SaveDisplay.
  194.   Restores the video display to its state prior to the
  195.   TSR popping up. }
  196. begin
  197.   { Restore screen content. }
  198.   VideoMem^ := SavedVideo;
  199.  
  200.   { Restore cursor shape. }
  201.   with CPURegisters do
  202.   begin
  203.     AH := $01;
  204.     CH := CursorStartLine;
  205.     CL := CursorEndLine;
  206.     Intr($10, CPURegisters);
  207.   end;
  208.  
  209.   { Resize window so the GotoXY (below) will work. }
  210.   Window (Lo(SavedWindMin)+1, Hi(SavedWindMin)+1,
  211.           Lo(SavedWindMax)+1, Hi(SavedWindMax)+1);
  212.   Gotoxy ( SavedX, SavedY );
  213.  
  214. end; { Restore Display }
  215.  
  216.  
  217. procedure TrapCriticalErrors;
  218. assembler;
  219. { INT 24H }
  220. {
  221. This handler is enabled only while the TSR is popped
  222. up on-screen.
  223.  
  224. This handler exists solely to catch any DOS
  225. critical errors and is a crude method of doing so. Since
  226. this routine does nothing, any critical errors that
  227. occur while the TSR is popped up are ignored--which
  228. could be very dangerous. Also, if another TSR or ISR
  229. pops up after this one, it may get the critical
  230. error that was intended for this TSR.
  231. NOTE: Normally, this could be an "interrupt" type
  232. procedure. However, Turbo Pascal pushes and then pops
  233. all registers prior to the IRET instruction. By writing
  234. this as an assembler routine, this generates the IRET
  235. directly, followed by one superfluous RET instruction
  236. generated by the assembler, resulting in substantial
  237. code savings.
  238. }
  239. asm
  240.   IRET
  241. end; { TrapCriticalErrors }
  242.  
  243.  
  244.  
  245.  
  246. procedure CBreakCheck;
  247. assembler;
  248. { INT 1BH }
  249.  
  250. { This routine results in a no operation; when hooked to
  251. the INT 1B Ctrl-Break interrupt handler, it causes
  252. nothing to happen when Ctrl-Break or Ctrl-C are pressed.
  253. This routine is hooked only when the TSR is popped up. }
  254. asm
  255.         IRET
  256. end; { CBreakCheck }
  257.  
  258.  
  259.  
  260.  
  261. function GetKey : Integer;
  262. { Pauses for input of a
  263. single keystroke from the keyboard and returns the ASCII
  264. value. In the case where an Extended keyboard key is
  265. pressed, GetKey returns the ScanCode + 256. The Turbo
  266. Pascal ReadKey function is called to perform the
  267. keystroke input. This routine returns a 0 when an Extended
  268. key has been typed (for example, left or right arrow)
  269. and we must read the next byte to determine the Scan
  270. code.
  271. }
  272. var
  273.   Ch : Char;
  274. begin
  275.   { While waiting for a key to be pressed, call
  276.   the INT $28 DOS Idle interrupt to allow background
  277.   tasks a chance to run. }
  278.   repeat
  279.     asm
  280.       int $28
  281.     end;
  282.   until KeyPressed;
  283.   Ch := ReadKey;
  284.   If Ord(Ch) <> 0 then
  285.     GetKey := Ord(Ch)   { Return normal ASCII value. }
  286.   else
  287.     { Read the DOS Extended SCAN code that follows. }
  288.     GetKey := Ord(ReadKey) + 256;
  289. end;{GetKey}
  290.  
  291.  
  292.  
  293.  
  294. procedure  DoPopUpFunction;
  295. { This procedure is the "guts" of the popup
  296.   application. You can code your own application here, if
  297.   you want. Be sure to read the text for important
  298.   restrictions on what can be written in a TSR.
  299.  
  300.   As implemented here, this popup displays a table
  301.   of ASCII values.
  302. }
  303.  
  304. const
  305.   UpperLeftX=10;
  306.   UpperLeftY=5;
  307.     { Define upper-left corner of TSR's popup window. }
  308.  
  309.   LowerLeftX=70;
  310.   LowerRightY=20;
  311.     { Define lower-right corner of TSR's popup window. }
  312.  
  313.   Width=LowerLeftX - UpperLeftX + 1;
  314.   Height=LowerRightY - UpperLeftY + 1;
  315.     { Calculated width and height of popup window. }
  316.   MinX=6;
  317.     { Distance from left edge to display ASCII table. }
  318.   RightEdge=8;
  319.     { Marks the right edge (Width-RightEdge) of table. }
  320.   MinY=4;
  321.     { Distance from top of window to start ASCII Table. }
  322.   ValuesPerLine=Width-8 - MinX;
  323.     { Computed number of ASCII values in each line. }
  324.   NumLines=255 div ValuesPerLine;
  325.     { Computed number of lines in the ASCII table. }
  326.   KEY_LEFTARROW = 331;
  327.     { Keystroke values for extended keyboard codes. }
  328.   KEY_RIGHTARROW = 333;
  329.   KEY_DOWNARROW = 336;
  330.   KEY_UPARROW = 328;
  331.   KEY_ESCAPE = 27;
  332.   KEY_ENTER = 13;
  333.  
  334.  
  335. procedure PutChar( X, Y: Integer; ChCode: Char );
  336. { Writes the single character ChCode to the screen
  337. at (X, Y), where (X, Y) is relative to the TSR popup
  338. window. This routine is used for displaying the ASCII
  339. table because the usual Pascal Write() translates ASCII
  340. 7 to a bell ring, and ASCII 13 and 10 to carriage return
  341. and line feed. By using the PC BIOS routine directly, we
  342. bypass Pascal's translation of these characters. }
  343. begin
  344.   with  CPURegisters  do
  345.   begin
  346.     { Move the cursor to adjusted (X, Y). }
  347.     AH := $02;
  348.     BH := 0;
  349.     DH := UpperLeftY + Y - 2;
  350.     DL := UpperLeftX + X - 2;
  351.     Intr($10, CPURegisters);
  352.  
  353.     { Output the character to the current cursor location. }
  354.     AH := $09;
  355.     AL := byte(ChCode);
  356.     BH := 0;
  357.     BL := 3 shl 4 + 14;
  358.       { Background=color 3; Foreground=color 14 }
  359.     CX := 1;
  360.     Intr($10, CPURegisters);
  361.   end;
  362. end; {PutChar}
  363.  
  364.  
  365. var
  366.   ASCIICode: Integer;
  367.     { Computed from X, Y location in table. }
  368.   I: Integer;
  369.     { For loop index variable. }
  370.   TextLine: String[Width];
  371.     { Buffer to hold width of window's text. }
  372.   X, Y: Integer;
  373.     { Tracks cursor location in ASCII table. }
  374.   Ch : Integer;
  375.     { Holds the keystroke typed. }
  376.  
  377. begin {DoPopUpFunction}
  378.   { Select White text on Cyan background for
  379.   { Set up a viewing window; makes calculation
  380.   of X, Y easier. }
  381.   Window(UpperLeftX, UpperLeftY, LowerLeftX, LowerRightY);
  382.  
  383.   { Enclose the window by drawing a border around
  384.   it and filling the interior with blanks. }
  385.   FillChar( TextLine[1], Width, ' ');
  386.   TextLine[0] := Chr( Width );
  387.   TextLine[1] := chr( 179 );
  388.   TextLine[Width] := chr( 179 );
  389.  
  390.   for I := 2 to Height - 2 do
  391.   begin
  392.     Gotoxy(1, I);
  393.     Write( TextLine );
  394.   end;
  395.  
  396.   FillChar( TextLine[1], Width, Chr(196));
  397.   TextLine[1] := Chr( 218 );
  398.   TextLine[Width] := Chr( 191 );
  399.   Gotoxy( 1 , 1 );
  400.   Write( TextLine );
  401.  
  402.   TextLine[1] := Chr( 192 );
  403.   TextLine[Width] := Chr( 217 );
  404.   Gotoxy ( 1, Height - 1 );
  405.   Write( TextLine );
  406.  
  407.   { Display window title }
  408.   Gotoxy ( Width div 2 -10, 2 );
  409.   Write( 'Table of ASCII Values' );
  410.  
  411.   { Draw the ASCII table on the display }
  412.   X := MinX;
  413.   Y := MinY;
  414.   for I := 0 to 255 do
  415.   begin
  416.     PutChar( X, Y, Chr(I) );
  417.     Inc(X);
  418.     If  X = (Width-RightEdge)  then
  419.     begin
  420.       Inc( Y );
  421.       X := MinX;
  422.     end;
  423.   end;
  424.  
  425.   Gotoxy ( Width div 2 - 20 , 11 );
  426.   Write('Use arrow keys to navigate; Esc when done');
  427.   X := MinX; Y := MinY;
  428.   repeat
  429.     { Compute ASCII code and value at X, Y }
  430.     ASCIICode := (X-MinX + (Y-MinY)*ValuesPerLine);
  431.     Gotoxy (Width div 2 - 11 , 13);
  432.     { NOTE: This allows display of
  433.     "ASCII codes" greater than 256 if cursor
  434.     moves into blank area in table. }
  435.     Write('Character= ', '  ASCII=',ASCIICode:3);
  436.     PutChar(Width div 2, 13, Chr(ASCIICode));
  437.     { Display that value, below }
  438.     Gotoxy ( X, Y );
  439.     Ch := GetKey;
  440.     Case  Ch  Of
  441.       KEY_LEFTARROW:  if  X > MinX  then  Dec(X);
  442.       KEY_RIGHTARROW:
  443.         if  X < (Width - RightEdge - 1)  then  Inc(X);
  444.       KEY_DOWNARROW: if  Y < (MinY+NumLines)  then Inc(Y);
  445.       KEY_UPARROW:  if    Y > MinY  then    Dec(Y);
  446.     end;
  447.   until  Ch = 27;
  448.  
  449.   { End of TSR popup code. }
  450. end; {DoPopUpFunction}
  451.  
  452.  
  453.  
  454. procedure RunPopUp;
  455. { Switches from system stack to TSR's stack. Calls
  456. DoPopUpFunction to run the actual TSR application. This
  457. keeps all the ugly details separate from the
  458. application. Note that while the TSR is up on the
  459. screen, and only while the TSR is up, we trap the
  460. Ctrl-Break and DOS critical errors interrupt. We do
  461. nothing when we see them except return, thereby ignoring
  462. the interrupts. }
  463. begin
  464.  { Switch stacks. }
  465.    asm
  466.      CLI
  467.    end;
  468.    SavedSS := SSeg;
  469.    SavedSP := SPtr;
  470.    asm
  471.      MOV   SS, OurSS
  472.      MOV   SP, OurSP
  473.      STI
  474.    end;
  475.    GetIntVec( $1B, PInt1B );
  476.      { Disable Ctrl-Break checking. }
  477.    SetIntVec( $1B, @CBreakCheck );
  478.    GetIntVec( $24, PInt24 );
  479.      { Trap DOS critical errors. }
  480.    SetIntVec( $24, @TrapCriticalErrors );
  481.    SaveDisplay;
  482.  
  483.    DoPopUpFunction;
  484.  
  485.    RestoreDisplay;
  486.    SetIntVec( $24, PInt24 );
  487.      { Reenable DOS critical error trapping. }
  488.    SetIntVec( $1B, PInt1B );
  489.      { Reenable Ctrl-C trapping. }
  490.    { Restore stacks. }
  491.    asm
  492.      CLI
  493.      MOV   SS, SavedSS
  494.      MOV   SP, SavedSP
  495.      STI
  496.    end;
  497. end; {RunPopUp}
  498.  
  499.  
  500. procedure BackgroundInt;
  501. interrupt;
  502. { INT 28H }
  503. { This routine is hooked in the DOS INT 28H chain,
  504. known variously as the DOSOK or DOSIdle interrupt. The
  505. idea is that when applications are doing nothing except
  506. waiting for a keystroke, they can call INT 28
  507. repeatedly. INT 28 runs through a chain of applications
  508. that each get a crack at running.
  509.  
  510. The keyboard interrupt handler watches for the
  511. magic TSR popup key. Some of the time it runs the TSR
  512. popup directly; when DOS is doing something, however, it
  513. can't run the TSR. So, it sets a flag saying "Hey, INT
  514. 28, if you see this flag set, then do the TSR." So the
  515. INT 28 code, here, examines the flag. If the flag is set, INT 28
  516. knows that the TSR was activated, so it calls it now.
  517. We can do this because DOS calls INT 28 only if it's safe
  518. for something else to run.
  519. }
  520. begin
  521.   { Call saved INT 28H handler. }
  522.   asm
  523.     PUSHF
  524.     CALL PInt28
  525.   end;
  526.   if  MadeActive  then
  527.   begin
  528.     TSRInUse := True;
  529.     MadeActive := False;
  530.     RunPopUp;
  531.     TSRInUse := False;
  532.   end;
  533. end; {BackgroundInt}
  534.  
  535.  
  536.  
  537. procedure KeyboardInt;
  538. interrupt;
  539. { INT 09H }
  540. { Examines all keyboard interrupts. First calls the
  541. existing interrupt handler. If our TSR is NOT currently
  542. running, then it checks for the magic pop keystrokes. If
  543. the TSR is already running, then we do not want to
  544. activate it again, so we ignore keystroke checking when
  545. the TSR is already alive and on-screen.
  546.  
  547. Several bytes in low memory contain keyboard status
  548. information. By checking the values in these bytes,
  549. various "not normal" key combinations can be detected.
  550. As implemented here, the TSR is made active by pressing
  551. the left Alt key, plus the SysRq key (Print Screen on my
  552. PC). You can change these keystrokes to something else.
  553. }
  554. const
  555.   CallTSRMask = 6;
  556.   { ACTIVATE TSR = left Alt key + SysRq key }
  557. var
  558.   ScanCode: byte absolute $40:$18;
  559.     { One of the keyboard status bytes. }
  560. begin
  561.   { Call existing keyboard interrupt handler. }
  562.   asm
  563.     PUSHF
  564.     CALL PINT09
  565.   end;
  566.  
  567.   if  not TSRInUse  then
  568.     if  (ScanCode and CallTSRMask) = CallTSRMask  then
  569.     begin
  570.       { The TSR has been activated. }
  571.       TSRInUse := True;
  572.         { Set to TRUE to prevent reactivation of this TSR. }
  573.       if  (PInDosFlag^ = 0)  then
  574.       begin
  575.         { If in "Safe" DOS area, then pop up now. }
  576.         MadeActive := False; { So INT $28 won't call us. }
  577.         RunPopUp;
  578.         TSRInUse := False;
  579.       end
  580.       else
  581.         MadeActive := True;
  582.           { o/w, set flag let INT 28 call us when ok. }
  583.     end;
  584. end; {KeyboardInt}
  585.  
  586.  
  587.  
  588.  
  589. procedure DoUnInstall ( var Removed: Boolean ); forward;
  590.  
  591.  
  592. { These are "local" to OurInt12; it is safer to store them in the
  593. TSR's global data area than to place them on the stack as
  594. local variables. }
  595. var
  596.   IdStr : ^String8;
  597.   MessageNum : Integer;
  598.  
  599. procedure OurInt12
  600.   (_AX, BX, CX, DX, SI, DI, DS, ES, BP:Word);
  601. interrupt;
  602. { INT 12H }
  603. { Intercepts INT 12H calls. If the ES:BX register
  604. just happens to point to IdStr1, then the command-line
  605. TSR program just called in. If this is the case, the
  606. other registers could be used to pass a message to the
  607. running TSR. Here, it's used by the running TSR to return
  608. a pointer to another string, confirming that the TSR is
  609. indeed running.
  610. }
  611.  
  612. var
  613.   DeInstallOk: Boolean;
  614. begin
  615.   IdStr := Ptr( ES, BX );
  616.     { Check to see if ES:BX points to the }
  617.     { magic ID string }
  618.   If  IdStr^ = IdStr1  then
  619.     MessageNum := CX
  620.   else
  621.   begin
  622.     MessageNum := 0;
  623.       { No message rcvd; this is normal DOS call. }
  624.     asm
  625.        pushf
  626.        call    PInt12
  627.        mov     _AX, ax
  628.          { AX returns the memory size value. }
  629.     end;
  630.   end;
  631.   if  MessageNum > 0   then
  632.   { Process a message directed to this TSR. }
  633.   begin
  634.     case  MessageNum  of
  635.       1:  begin
  636.           { Returns pointer to IdStr2 indicating this
  637.           TSR is here. }
  638.             ES := Seg(IdStr2);
  639.             BX := Ofs(IdStr2);
  640.           end;
  641.       2:  begin { Performs request to uninstall the TSR. }
  642.             DoUnInstall (DeInstallOk);
  643.             if   DeInstallOk  then
  644.               CX := 0 { Report success. }
  645.             else
  646.               CX := 1; { Report failure. }
  647.           end;
  648.     end;
  649.   end;
  650. end; {OurInt12}
  651.  
  652.  
  653.  
  654. procedure DoUnInstall ( var Removed: Boolean );
  655. begin
  656.   { See if any TSRs have loaded in memory after us.
  657.   If another TSR has loaded after us, then we really
  658.   cannot safely terminate this TSR. Why? Because when they
  659.   terminate, they may reset their interrupts to point back
  660.   to this TSR. And if this TSR is no longer in memory,
  661.   uh-oh... }
  662.  
  663.   Removed := True;
  664.   GetIntVec( $28, TempPtr );
  665.   if  TempPtr <> @BackgroundInt  then
  666.     Removed := False;
  667.   GetIntVec( $12, TempPtr );
  668.   If  TempPtr <> @OurInt12  then
  669.     Removed := False;
  670.  
  671.   GetIntVec( $09, TempPtr );
  672.   if  TempPtr <> @KeyBoardInt  then
  673.     Removed := False;
  674.  
  675.   if  Removed  then
  676.   begin
  677.     { Restore interrupts }
  678.     SetIntVec( $28, PInt28 );
  679.     SetIntVec( $12, PInt12 );
  680.     SetIntVec( $09, PInt09 );
  681.  
  682.     { Free up memory allocated to this program using
  683.     INT 21 Func=49H "Release memory". }
  684.     CPURegisters.AH := $49;
  685.     CPURegisters.ES := PrefixSeg;{ Current program's PSP }
  686.     Intr( $21, CPURegisters );
  687.   end;
  688. end; {DoUnInstall}
  689.  
  690.  
  691.  
  692.  
  693.  
  694. procedure InstallTSR (var AlreadyInstalled: Boolean );
  695. { Installs all interrupt handlers for this TSR. }
  696.  
  697. var
  698.   PSPPtr : ^Word;
  699.   IdStr : ^String8;
  700. begin
  701.  
  702.   { Check to see if the TSR is already running by
  703.   executing INT 12 with ES:BX pointing to IdStr1. If,
  704.   after calling INT 12, ES:BX points to IdStr2, then the
  705.   TSR is running since it must have intercepted INT 12 and
  706.   set ES:BX to those return values. }
  707.  
  708.   with  CPURegisters  do
  709.   begin
  710.     ES := Seg(IdStr1);
  711.     BX := Ofs(IdStr1);
  712.     CX := 1;
  713.     Intr( $12, CPURegisters );
  714.     IdStr := Ptr( ES, BX );
  715.     if  IdStr^ = IdStr2  then
  716.     begin
  717.        AlreadyInstalled := True;
  718.        Exit;
  719.     end;
  720.     { TSR hasn't been installed, so install our
  721.     INT 12 driver. }
  722.     asm
  723.       cli
  724.     end;
  725.     GetIntVec( $12, PInt12 );
  726.     SetIntVec( $12, @OurInt12 );
  727.     asm
  728.       sti
  729.     end;
  730.   end;
  731.   AlreadyInstalled := False;
  732.   MadeActive := False;
  733.   DiskInUse := 0;
  734.  
  735.   { Check to see if TSR is already installed. }
  736.   TSRInUse := False;
  737.  
  738.   { Deallocate DOS Environment block if not needed. }
  739.   { Comment out this code if you need to access
  740.     environment strings. }
  741.   PSPPtr := Ptr( PrefixSeg, $2C );
  742.   CPURegisters.AX := $4900;
  743.   CPURegisters.ES := PSPPtr^;
  744.   Intr( $21, CPURegisters);
  745.  
  746.   asm
  747.     cli
  748.   end;
  749.  
  750.   { Save and set INT 28H, background process interrupt. }
  751.   GetIntVec( $28, PInt28 );
  752.   SetIntVec( $28, @BackgroundInt );
  753.  
  754.   { Save and set INT 09H, the keyboard interrupt handler. }
  755.   GetIntVec( $09, PInt09 );
  756.   SetIntVec( $09, @KeyboardInt );
  757.   asm
  758.     sti
  759.   end;
  760.  
  761.   { Initialize pointer to DOS's InDos flag. }
  762.   { Uses INT 21H Function 34H to retrieve a pointer to the
  763.   InDos flag. The result is returned in the ES:BX
  764.   registers. }
  765.   CPURegisters.AH := $34;
  766.   Intr( $21, CPURegisters );
  767.   PInDosFlag := Ptr( CPURegisters.ES, CPURegisters.BX );
  768.  
  769.   asm
  770.     CLI
  771.   end;
  772.   { Save our SS:SP for later use. }
  773.   OurSS := SSeg;
  774.   OurSP := SPtr;
  775.   asm
  776.     STI
  777.   end;
  778. end; {InstallTSR}
  779.  
  780. var
  781.   InstallError: Boolean;
  782.  
  783. begin {program}
  784.   InstallTSR( InstallError );
  785.   if  InstallError  then
  786.   begin
  787.     { This means that the TSR is already running.
  788.       In that case, see if the command line requests
  789.       an uninstall. }
  790.     if  (ParamStr(1) = '/u') or (ParamStr(1) = '/U')  then
  791.       { Send an Uninstall message to the TSR }
  792.       with  CPURegisters  do
  793.       begin
  794.         ES := Seg(IdStr1);
  795.         BX := Ofs(IdStr1);
  796.         CX := 2;
  797.         Intr( $12, CPURegisters );
  798.         if  CX = 0  then
  799.           Writeln('TSR is now uninstalled.')
  800.         else
  801.           Writeln('Unable to uninstall.');
  802.         Exit;
  803.       end
  804.     else
  805.       Writeln('!!!TSR is already installed!!!');
  806.   end
  807.   else
  808.   begin
  809.     Writeln('TSR is now resident.');
  810.     Keep( 0 );
  811.      { Exit to DOS, leaving program memory-resident. }
  812.   end;
  813. end. {program}
  814.